package com.gaofans.nrpc.client.bootstrap;

import com.gaofans.nrpc.client.handler.*;
import com.gaofans.nrpc.common.context.SelectableExecutorGroup;
import com.gaofans.nrpc.common.context.SelectableExecutor;
import com.gaofans.nrpc.common.exception.ExceptionHandler;
import com.gaofans.nrpc.common.id.IdGenerator;
import com.gaofans.nrpc.common.processor.ServiceAuthority;
import com.gaofans.nrpc.common.protocol.EchoMessageHandler;
import com.gaofans.nrpc.common.protocol.MessageCodec;
import com.gaofans.nrpc.common.protocol.ProtocolFrameDecoder;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.timeout.IdleStateHandler;

import javax.net.ssl.SSLEngine;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @author GaoFans
 */
@SuppressWarnings({"rawtypes","unchecked"})
public class ChannelBootstrap implements AutoCloseable{

    private final NioEventLoopGroup executors;
    private final ResponseEventProcessor processor;
    private final List<ClientFilter> filters;
    private final DefaultEventLoopGroup worker;
    private final SelectableExecutor chooser;
    private final LoggingHandler loggingHandler;
    private final MessageCodec codec;
    private final ContextBindHandler contextBindHandler;
    private final RpcResponseHandler responseHandler;
    private final ExceptionHandler exceptionHandler;
    private final CloseEventHandler closeEventHandler;
    private final EchoMessageHandler echoMessageHandler;
    private final Bootstrap bootstrap;
    private final IdGenerator idGenerator;
    public ChannelBootstrap(ChannelBootstrapConfig config) {
        this.executors = new NioEventLoopGroup(config.getEventLoopThreadCount());
        this.processor = new ResponseEventProcessor(Objects.requireNonNull(config.getProcessorExpireTime()), Math.max(config.getProcessorMaximumSize(),16));
        this.filters = new CopyOnWriteArrayList<>();
        this.worker = new DefaultEventLoopGroup(config.getWorkerThreadCount());
        this.chooser = new SelectableExecutorGroup(Math.max(config.getHandlerThreadCount(),2));
        this.loggingHandler = new LoggingHandler();
        this.codec = new MessageCodec(config.getSerializerFactory(), config.getSerializerType(), config.getVersion(), config.getCodec());
        this.contextBindHandler = new ContextBindHandler(chooser);
        this.responseHandler = new RpcResponseHandler(processor);
        this.exceptionHandler = new ExceptionHandler();
        this.closeEventHandler = new CloseEventHandler();
        this.echoMessageHandler = new EchoMessageHandler(config.getIdGenerator());
        this.idGenerator = config.getIdGenerator();
        this.bootstrap = new Bootstrap()
                .group(executors)
                .channel(NioSocketChannel.class);
        Map<ChannelOption, Object> optionMap = config.getOptionMap();
        if (optionMap != null && optionMap.size() > 0){
            for (Map.Entry<ChannelOption, Object> entry : optionMap.entrySet()) {
                bootstrap.option(entry.getKey(),entry.getValue());
            }
        }
        bootstrap
                .handler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) {
                        if (!Objects.isNull(config.getSslContext())){
                            SSLEngine engine = config.getSslContext().newEngine(ch.alloc());
                            engine.setUseClientMode(true);
                            ch.pipeline().addLast(new SslHandler(engine));
                        }
                        ch.pipeline().addLast(new ProtocolFrameDecoder());
                        ch.pipeline().addLast(loggingHandler);
                        ch.pipeline().addLast(codec);
                        ch.pipeline().addLast(new IdleStateHandler(config.getReaderIdleTimeSecond(), config.getWriterIdleTimeSecond(), 0));
                        ch.pipeline().addLast(echoMessageHandler);
                        ch.pipeline().addLast(contextBindHandler);
                        ch.pipeline().addLast(worker,new RequestOutFilterHandler(filters));
                        ch.pipeline().addLast(worker,responseHandler);
                        ch.pipeline().addLast(closeEventHandler);
                        ch.pipeline().addLast(exceptionHandler);
                    }
                });
    }

    public ChannelFuture bootstrap(ServiceAuthority info){
        return bootstrap.connect(info.getHost(), info.getPort());
    }

    @Override
    public void close() {
        executors.shutdownGracefully();
        worker.shutdownGracefully();
        chooser.shutdownGracefully();
    }

    public void addFilter(ClientFilter filter){
        filters.add(filter);
    }



    public ResponseEventProcessor processor() {
        return this.processor;
    }

    public SelectableExecutor chooser() {
        return this.chooser;
    }

    public CloseEventHandler closeEventHandler() {
        return closeEventHandler;
    }

    public IdGenerator idGenerator() {
        return idGenerator;
    }
}
